#include "util/mypinger.h"
#include<stdlib.h>
namespace nyutil
{

    myPinger::myPinger()
    {

    }
    myPinger::~myPinger(){
        memset(send_buff,0,BUFFERSIZE);
        memset(recv_buff,0,2*K);
        memset(dest_str,0,80);
        memset(res,0,80);
        rawsock = 0;
        alive = 0;                            
        isSuccess = 0;                           
        packet_send = 0;                    
        packet_recv = 0;  
    }

    /*终端信号处理函数SIGINT
    void  myPinger::icmp_sigint(int signo)
    {
        alive = 0;
        gettimeofday(&tv_end,NULL);
        tv_interval = icmp_tvsub(tv_end, tv_begin);
    
        return;
    }*/

    /*统计数据结果函数******************************************
    * 打印全部ICMP发送的接收统计结果
    void  myPinger::icmp_statistics(void)
    {
        long time = (tv_interval.tv_sec * 1000) + (tv_interval.tv_usec/1000);
        printf("--- %s ping statistics ---\n", dest_str);
        printf("%d packets transmitted, %d received, %d%c packet loss, time %ld ms\n", 
            packet_send,packet_recv,(packet_send-packet_recv)*100/packet_send,"%",time);
    }
    */
    /*************查找数组中的标识函数***********************
     *  查找合适的包的位置
     *   当seq为1时，表示查找空包
     *    其他值表示查找seq对应的包*/
    pingm_pakcet* myPinger::icmp_findpacket(int seq)
    {
        int i;
        pingm_pakcet *found = NULL;
        if(seq == -1){
            for(i=0;i<128;i++){
                if(pingpacket[i].flag == 0){
                    found = &pingpacket[i];
                    break;
                }
            }
        }
        else if(seq >= 0){
            for(i =0 ;i< 128;i++){
                if(pingpacket[i].seq == seq){
                    found = &pingpacket[i];
                    break;
                }
            }
        }
        return found;
    }

    /*************校验和函数*****************************
     * TCP/IP协议栈使用的校验算法是比较经典的,对16位的数据进行累加计算,并返回计算结果,
     * CRC16校验和计算icmp_cksum
     * 参数:
     *  	data:数据
     *  	       len:数据长度
     *  	       返回值:
     *  	           计算结果,short类型
     *  	           */
    unsigned short myPinger::icmp_cksum(unsigned char *data, int len)
    {
        int sum = 0;   
        int odd = len & 0x01;  
        /*将数据按照2字节为单位累加起来*/
        while(len & 0xfffe){
            sum += *(unsigned short*)data;
            data += 2;
            len -= 2;
        }
        /*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一个字节*/
        if(odd){
            unsigned short tmp = ((*data)<<8)&0xff00;
            sum += tmp;
        }
        sum = (sum >> 16) + (sum & 0xffff);   
        sum += (sum >> 16);                   
                                            
        return ~sum;                          
    }

    /**********进行ICMP头部校验********************/
    void myPinger::icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length)
    {
        unsigned char i = 0;
        icmph->icmp_type = ICMP_ECHO;  
        icmph->icmp_code = 0;          
        icmph->icmp_cksum = 0;         
        icmph->icmp_seq = seq;         
        icmph->icmp_id = pid & 0xffff; 
        for(i=0; i< length; i++)       
            icmph->icmp_data[i] = i;   
        icmph->icmp_cksum = icmp_cksum((unsigned char*)icmph, length);
    }
    
    /*解压接收到的包，并打印信息*/
    int myPinger::icmp_unpack(unsigned char *buf, int len)
    {
        int i,iphdrlen;
        struct ip *ip = NULL;
        struct icmp *icmp = NULL;
        int rtt;
    
        ip = (struct ip *)buf;           
        iphdrlen = ip->ip_hl * 4;        
        icmp = (struct icmp *)(buf+iphdrlen);  
        len -= iphdrlen;
        if(len < 8){
            printf("ICMP packets\'s length is less than 8\n");
            return -1;
        }
        if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)){
            struct timeval tv_interval,tv_recv,tv_send;
            
            pingm_pakcet *packet = icmp_findpacket(icmp->icmp_seq);
            if(packet == NULL)
                return -1;
            packet->flag = 0;          
            tv_send = packet->tv_begin;  
    
            gettimeofday(&tv_recv,NULL);  
            tv_interval = icmp_tvsub(tv_recv,tv_send);
            rtt = tv_interval.tv_sec * 1000 + tv_interval.tv_usec/1000;
            /*打印结果包含
    * 		  ICMP段的长度
    * 		  		  源IP地址
    * 		  		  		  包的序列号
    * 		  		  		  		  TTL
    * 		  		  		  		  		  时间差
    * 		  		  		  		  		  		*/
            printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n", 
                len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rtt);
            packet_recv ++;
           return 1;
                   
        }
        else {
            return -1;
        }
        return -1;
        
    }

    /************计算时间差time_sub************************
     * 参数：
     * 	end:接收到时间
     * 		begin:开始发送的时间
     * 		返回值:
     * 		 	使用的时间
     * 		 	*/
    struct timeval myPinger::icmp_tvsub(struct timeval end, struct timeval begin)
    {
        struct timeval tv;
        
        tv.tv_sec = end.tv_sec - begin.tv_sec;
        tv.tv_usec = end.tv_usec - begin.tv_usec;
        if(tv.tv_usec < 0){
            tv.tv_sec --;
            tv.tv_usec += 1000000;
        }
    
        return tv;
    }

    int myPinger::icmp_send(int nsec)
    {
        gettimeofday(&tv_begin, NULL);
        while(nsec){
            int size = 0;
            struct timeval tv;
            gettimeofday(&tv, NULL);     
            pingm_pakcet *packet = icmp_findpacket(-1);
            if(packet){
                packet->seq = packet_send;
                packet->flag = 1;
                gettimeofday(&packet->tv_begin,NULL);
            }
            icmp_pack((struct icmp *)send_buff,packet_send,&tv, 64);
            size = sendto(rawsock, send_buff,64,0,(struct sockaddr *)&dest, sizeof(dest));
            if(size < 0){
                perror("sendto error");
                return -1;
            }
            packet_send ++;
            nsec--;
        }
        return 1;
    }

    int myPinger::icmp_recv(int nsec)
    {
        struct timeval tv;
        tv.tv_usec = 200;
        tv.tv_sec = 0;
        fd_set readfd;
        while(nsec){
            int ret = 0;
            FD_ZERO(&readfd);
            FD_SET(rawsock,&readfd);
            ret = select(rawsock+1,&readfd,NULL,NULL,&tv);
            switch(ret)
            {
                case -1:
                    break;
                case 0:
                    break;
                default :
                    {
                        int fromlen = 0;
                        struct sockaddr from;
                        int size = recv(rawsock,recv_buff,sizeof(recv_buff),0);
                        if(errno == EINTR){
                            perror("recvfrom error");
                            continue;
                        }
                        ret = icmp_unpack(recv_buff,size);
                        if(ret == 1){
                            return 1;
                        }
                    }
                    break;
            }
            nsec--;
        }
        return -1;
    }

    int myPinger::doPing(char const *ipAddr,int nsec)
    {
        struct hostent *host = NULL;
        struct protoent *protocol = NULL;
        char protoname[] = "icmp";
        unsigned long inaddr = 1;
        int size = 128*K;
                                            
        protocol = getprotobyname(protoname);
        if(protocol == NULL)
        {
            perror("getprotobyname()");
            return 0;
        }
                                            
        memcpy(dest_str, ipAddr,strlen(ipAddr)+1);
        memset(pingpacket, 0, sizeof(pingm_pakcet) * 128);
                                            
        rawsock = socket(AF_INET, SOCK_RAW, protocol->p_proto);
        if(rawsock < 0){
            perror("socket");
            return 0;
        }
    
        pid = getuid();                      
                                            
        setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
        bzero(&dest, sizeof(dest));
                                            
        dest.sin_family = AF_INET;
                                            
        inaddr = inet_addr(ipAddr);
        if(inaddr == INADDR_NONE){             
            host = gethostbyname(ipAddr);
            if(host == NULL){
                perror("gethostbyname");
                return 0;
            }
                                                
            memcpy((char *)&dest.sin_addr, host->h_addr, host->h_length);
        }                                      
        else {
            memcpy((char *)&dest.sin_addr, &inaddr,sizeof(inaddr));
        }
                                            
        inaddr = dest.sin_addr.s_addr;
        printf("PING %s (%ld.%ld.%ld.%ld) 56(84) bytes of data.\n", 
            dest_str,(inaddr&0x000000ff)>>0,(inaddr&0x0000ff00)>>8,(inaddr&0x00ff0000)>>16,(inaddr&0xff000000)>>24);
                                            
        //signal(SIGINT,icmp_sigint);
    
        /*发送数据并接收回应
    * 	建立两个线程，一个用于发数据，另一个用于接收响应数据，主程序等待两个线程运行完毕后再进行
    * 		下一步，最后对结果进行统计并打印
    * 			*/
        alive = 1;                                    
        int err = 0;
        int tserc = nsec;
        while(tserc)
        {
            sleep(1);//等待一秒机器，防止机器启动慢
            err=icmp_send(nsec);//err = pthread_create(&send_id, NULL, icmp_send, NULL); 
            if(err < 0){
                return 0;
            }
            err=icmp_recv(nsec);//err = pthread_create(&recv_id, NULL, icmp_recv, NULL); 
            if(err < 0){
                return 0;
            }
            if(err == 1)
                break;
            sleep(1);
            tserc--;
        }
        

                                    
        close(rawsock);
        if(err == 1)    
        {
            return 1;
        }
       return 0;
            
    }
};